El paquete ggplot toma decisiones por default cuando genera un gráfico o visualización. Por ejemplo, el color de los ítems con los que representa los datos, el rango numérico de los ejes (en que número empiezan y terminan) o el color de fondo. Lo anterior, permite realizar visualizaciones rápidas escribiendo pocas líneas de código. Sin embargo, a la hora de compartir nuestras visualizaciones con el mundo vale la pena hacer ajustes para obtener mejores resultados.
En esta clase iremos más allá del aspecto que tienen los gráficos de ggplot por defecto, para ello vamos a pulir nuestras visualizaciones para su publicación.
Para empezar, activamos ggplot2:
library(ggplot2)
El data set con el que trabjaremos esta clase fue compilado por Gapminder, una ONG sueca dedicada a explicar el mundo con datos. La base de datos contiene indicadores de desarrollo de países en todo el mundo, con observaciones en intervalos de 5 años:
head(gapminder)
## pais continente año expVida pobl PBIpc
## 1 Afghanistan Asia 1952 28.801 8425333 779.4453
## 2 Afghanistan Asia 1957 30.332 9240934 820.8530
## 3 Afghanistan Asia 1962 31.997 10267083 853.1007
## 4 Afghanistan Asia 1967 34.020 11537966 836.1971
## 5 Afghanistan Asia 1972 36.088 13079460 739.9811
## 6 Afghanistan Asia 1977 38.438 14880372 786.1134
Los gráficos de dispersión o scatterplots son quizás el tipo de visualización más conocido.
Los gráficos de dispersión visualizan la relación entre dos variables numéricas, de forma que una variable se muestra en el eje x y la otra, en el eje y. En otras palabras, consiste en puntos proyectados en un eje de coordenadas (“x” e “y”), donde cada punto representa una observación. Estos gráficos son útiles para mostrar la correlación entre dos variables numéricas.
Por ejemplo, podríamos comparar la relación entre la riqueza de los países (medida como PBI per capita) y la salud de sus habitantes (como expectativa de años de vida). Esa es la visualización de datos que popularizó Hans Rosling.
Hans Rosling fue el médico, fanático de la estadística, optimista y entusiasta comunicador que fundó el proyecto Gapminder, cuya misión declarada es “luchar contra la ignorancia devastadora con una visión del mundo basada en hechos, que todo el mundo pueda entender”.
Antes de seguir, dediquemos cuatro minutos a mirar “200 años, 200 países, 4 minutos”, un resumen ilustrado de los últimos dos siglos de desarrollo económico que Rosling preparó con ayuda de la BBC.
Intentemos reproducir la visualización de Rosling. Con lo que
aprendimos hasta aquí, deberíamos poder realizar un gráfico de
dispersión básico -con geom_point()- ubicando la variable
“PBIpc” en el eje de las \(x\), y
“expVida” en el eje de las \(y\):
ggplot(gapminder, aes(x = PBIpc, y = expVida)) +
geom_point()
Hmmm… ¡no se ve muy parecido!.
Lo que ocurre es que Rosling utiliza un truco: usa una escala logarítmica.
Si revisamos la imagen de Rosling presentando que aparece más arriba, vemos que en el eje de las \(y\) la escala salta de 400 a 4.000 y luego a 40.000. En segmentos del mismo largo, los valores no crecen proporcionalmente sino en potencias de 10. Esto se logra tomando el logaritmo en base 10 de los datos antes de proyectarlos, con lo cual lo que estamos mostrando es 4 x 101, 4 x 102, 4 x 103, etc.
¿Para que sirve esto? Para visualizar datos en los que existen grandes disparidades en una variable, porque permite que los valores pequeños tengan espacio para diferenciarse y, a la vez, que los valores grandes no aparezcan tan alejados. Logrando, de esta manera, un gráfico más compacto y en general más legible.
Cuando hay dinero involucrado suele ser necesario invocar a la escala logarítmica para mejorar la legibilidad… ¡las grandes disparidades abundan!
En todo caso, ggplot2 incluye varias funciones para
transformar las escala de las \(x\) o
de las \(y\), entre ellas las que pasan
las variables a escala logarítmica de base 10:
scale_x_log10() y scale_y_log10(). ¿Cual
usaríamos aquí?
ggplot(gapminder, aes(x = PBIpc, y = expVida)) +
geom_point() +
scale_x_log10()
Las funciones que empiezan con scale_y_ o
scale_x_ generalmente sirven para modificar valores en una
escala. A su vez, tiene varios parámetros modificables. Por ejemplo,
tenemos el parámetro labels en el cual puedo especificar
las etiquetas del eje. Con la ayuda del paquete scales
podemos mostrar nuestros valores como números con comas.
ggplot(gapminder, aes(x = PBIpc, y = expVida)) +
geom_point() +
scale_x_log10(labels=scales::comma)
Ahora se vislumbra una disposición similar a la del gráfico del video, pero en nuestro caso parece haber demasiados puntos. Esto es porque nuestra data incluye 12 observaciones para cada país (cada 5 años desde 1952 hasta 2007). ¡O sea que cada país aparece 12 veces! El Dr. Rosling usaba sólo un punto por país, usando animación para mostrar distintos años.
Vamos a filtrar los datos para quedarnos sólo con la observación más
reciente disponible, la de 2007. Existe muchas formas de hacer esto en
R, pero aquí vamos a usar la función filter() del paquete
dplyr.
Si no conocen esta herramienta, o necesitan un repaso rápido, pueden revisar “transformando los datos”.
Por ahora la vamos a usar de forma muy simple: en lugar de llamar a la variable gapminder, vamos a usar el resultado de filtrar su contenido de forma tal que solo usemos las filas donde la variable “año” toma el valor 2007.
Activamos el paquete dplyr
library(dplyr)
Y ahora ya podemos usar su función filter() para
modificar nuestro dataset:
ggplot(filter(gapminder, año == 2007), aes(x = PBIpc, y = expVida)) +
geom_point() +
scale_x_log10()
Ahí va queriendo. Desviándonos un poco de nuestro objetivo original
de replicar a nuestro amigo Hans, podríamos querer agregar un intervalo
de confianza para nuestros puntos. Esto lo podemos lograr con
geom_smooth().
ggplot(filter(gapminder, año == 2007), aes(x = PBIpc, y = expVida)) +
geom_point() +
geom_smooth(method = "lm") +
scale_x_log10()
## `geom_smooth()` using formula = 'y ~ x'
geom_smooth() me muestra una línea de tendencia de las
observaciones en el gráfico con el intervalo de confianza para cada
grupo. Cuantas menos observaciones tenga para cada grupo, más grande
será el intervalo. Se pueden aplicar distintas funciones para aplicar la
línea de tendencia.
Necesitamos dos ajustes más para que el parecido sea evidente. Rosling usa el tamaño de los puntos para indicar la población del país y el color para diferenciar entre continentes. Con lo que aprendimos en la clase anterior podemos animarnos: las variables que se mostrarán con esos atributos estéticos son “pobl” y “continente”
ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida, size = pobl, color = continente)) +
geom_point() +
scale_x_log10()
¡Eso si es un plot gapmindereano!
¿Qué pasaría si quisieramos agregar un intervalo de confianza en este gráfico?
ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida, size = pobl, color = continente)) +
geom_point() +
geom_smooth(method = "lm")+
scale_x_log10()
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## `geom_smooth()` using formula = 'y ~ x'
## Warning in qt((1 - level)/2, df): Se han producido NaNs
## Warning: The following aesthetics were dropped during statistical transformation: size.
## ℹ This can happen when ggplot fails to infer the correct grouping structure in
## the data.
## ℹ Did you forget to specify a `group` aesthetic or to convert a numerical
## variable into a factor?
## Warning in max(ids, na.rm = TRUE): ningun argumento finito para max; retornando
## -Inf
:( ¿Por qué no se ve como quisieramos?
Esto tiene que ver con el mapeo de las observaciones. En la primera
línea de ggplot definimos que queríamos que los atributos de color y
tamaño se mapeen para todas las geometrías que tenemos. De esta manera,
geom_smooth()` está haciendo la línea de tendencia para
todos los grupos por su color. Por ello, si queremos mantener la
tendencia global debemos hacer unos pequeños ajustes en nuestro
gráfico.
ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida)) +
geom_point(aes(size = pobl, color = continente)) +
geom_smooth(method = "lm")+
scale_x_log10()
## `geom_smooth()` using formula = 'y ~ x'
Pero esto no termina aquí. Quedan bastantes cosas para ajustar antes de que nuestro querido gráfico esté listo para ser compartido con el mundo.
En el video de la BBC, la visualización se presenta sin leyendas para
reducir al mínimo el texto en pantalla y que se pueda mirar como casi un
cuadro. Para eliminar leyendas en nuestro ggplot podemos usar
guides y dentro asignar el valor "none" a cada
atributo estético para el cual no queremos leyenda. Por ejemplo,
color = "none".
¿Cómo eliminamos la leyenda de los atributos color y tamaño?
ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida, size = pobl, color = continente)) +
geom_point() +
scale_x_log10() +
guides(size = "none", color = "none")
Con frecuencia preferimos quedarnos con la leyenda para invitar que la audiencia compare los valores representados. En ese caso es necesario asegurarse que los valores mostrados sean razonablemente fáciles de interpretar.
En nuestro gráfico, algo que podemos mejorar es la representación de los números muy grandes: los millones de personas de la población. Por defecto, R usa notación científica para abreviar los números muy grandes (o muy anchos, deberíamos decir: los números ínfimos, con muchos espacios decimales en cero, también se abrevian).
Por eso en nuestro gráfico aparecen valores como “1.25e+09”, que significa “1,25 * 10 elevado a la 9”, o sea 1.250 millones. La brevedadad es buena a la hora de comunicar, pero no cuando se trata de números - la escala para la variable de población, en notación científica, resulta muy difícil de interpretar para la mayoría de las personas:
ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida, size = pobl, color = continente)) +
geom_point() +
scale_x_log10()
Se puede hacer que R se abstenga de abreviar usando la notación
científica (ejecutando la línea options(scipen=999)). Pero
una solución aún mejor, cuando de visualización se trata, es la de
convertir unidades. Por ejemplo, si los valores de población alcanzan
valores tan grandes, representemos millones de personas y con
eso los valores van a ser mas pequeños: en lugar de mostrar 1.000.000,
ahora veremos solo “1” (porque la unidad es “millones de
habitantes”).
Entonces nos quedamos con las leyendas (ya no las deshabilitamos con
guides()), y le podemos pedirle a ggplot que el tamaño de
los puntos ya no represente los valores de la columna “pobl”, si no el
resultado de los valores de la columna divididos por un millón. ¿Cómo
sería?
ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida, size = pobl/1000000, color = continente)) +
geom_point() +
scale_x_log10()
Si comparamos nuestros resultados con los de la visualización televisada por la BBC, notaremos que los colores usados para representar la variable “continente” son distintos. Esto es porque Rosling y su equipo los eligieron a su gusto, mientras que nosotros estamos usando los colores que elige ggplot de forma automática.
En la próxima clase veremos como usar escalas de colores
pre-diseñadas, con colores elegidos por sus propiedades ideales para
usar en visualización. Pero ahora mismo tenemos la oportunidad de elegir
de forma explícita los colores que queremos usar para replicar los del
ejemplo. Al cumplirse un minuto del video oímos a
Hans nombrar los colores que usa: “Europe brown, Asia Red…”. Con eso
podemos armar una lista con valores y sus colores asignados para pasarle
a ggplot().
Tomándonos nada menos que tres licencias. No vamos a asignarle un color específico a los países del Medio Oriente porque no aparecen diferenciados en nuestra data. Vamos a elegir un color para los dos países de Oceanía que tenemos y para Europa vamos a elegir un naranja oscuro, porque ese parece ser el color que usaron en lugar de marrón.
En R tenemos 657 colores que podemos llamar por nombre, como “hotpink” o “aquamarine”. Como es imposible recordarlos a todos, se puede recurrir a una guía de colores para encontrar los que necesitamos. Habiendo identificado nuestros colores, definimos la escala manual así:
color_continentes <- c("Europe" = "darkorange", "Asia" = "red", "Africa" = "blue",
"Americas" = "yellow", "Oceania" = "purple")
Y luego podemos usarla para indicarle a ggplot los colores exactos
que queremos usar. Para eso sirve scale_color_manual(), con
su parámetro “values”. Probemos:
ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida, size = pobl/1000000, color = continente)) +
geom_point() +
scale_x_log10() +
scale_colour_manual(values = color_continentes)
Hasta aquí hemos podido ajustar los atributos gráficos que dependen estrechamente de los datos, como las escalas, los colores y los tamaños.
¿Pero que hay de otros componentes visuales que podríamos cambiar, como la tipografía y su tamaño o el color de fondo? Esos atributos corresponden al “tema” (theme en inglés) que apliquemos. Los temas son como “packs” de parámetros que definen el aspecto de todos los componentes no relacionados con los datos y pueden cambiar drásticamente el look de nuetros gráficos.
El paquete ggplot2 incluye varias funciones que aplican
temas
predefinidos, como “theme_dark()” (la opción por defecto),
“theme_dark”, “theme_void”, “theme_minimal”, etc. Por eso para usar los
temas alcanza con sumar una línea que llame a la función
correspondiente. Por ejemplo, para usar “theme_dark”:
ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida, size = pobl/1000000, color = continente)) +
geom_point() +
scale_x_log10() +
scale_colour_manual(values = color_continentes) +
theme_dark()
Incluso podemos definir a mano cada componente visual de los gráficos
creando así nuestro propio tema. El camino comienza por el uso de la función
theme. Si entramos a la documentación vemos que tenemos
muchos parámetros que podemos modificar en ella, por lo que
abordarlos en su totalidad es un poco difícil ya que el uso que le demos
va a depender de lo que querramos hacer.
Veamos un pequeño ejemplo, cambiando de lugar la ubicación de las
leyendas con el parámetro legend.position.
ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida, size = pobl/1000000, color = continente)) +
geom_point() +
scale_x_log10() +
scale_colour_manual(values = color_continentes) +
theme_dark()+
theme(legend.position = "bottom")
(¡Ojo! Si quiero aplicar un parámetro de la función theme manteniendo un theme predeterminado siempre tiene que ir después de él).
También existen paquetes de R que agregan a nuestro arsenal nuevos
temas listos para usar, como ggthemes
y hrbrthemes.
Para cerrar el tema de los temas (¡ja!) va una recomendación. El tema
minimalista “theme_minimal” viene con ggplot2 por lo que ya
lo tenemos disponible y con sólo agregar la línea que lo aplica
obtenemos un gráfico de aspecto más “limpio” que el del tema por
default:
ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida, size = pobl/1000000, color = continente)) +
geom_point() +
scale_x_log10() +
scale_colour_manual(values = color_continentes) +
theme_minimal()
¿Cómo haríamos si quisiera ver dónde se ubican los países de las Américas en este mapa?
Vamos a introducir un paquete bastante útil: ggrepel.
Está diseñado específicamente para poder agregar etiquetas en gráficos
de ggplot sin que se nos pisen entre ellas. Probemos usar su función
geom_text_repel:
library(ggrepel)
ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida, size = pobl/1000000, color = continente)) +
geom_point() +
geom_text_repel(data = gapminder %>% filter(año == 2007 & continente == "Americas"),
aes(x = PBIpc, y = expVida, label = pais), inherit.aes = FALSE)+
scale_x_log10() +
scale_colour_manual(values = color_continentes) +
theme_minimal()
## Warning: ggrepel: 7 unlabeled data points (too many overlaps). Consider
## increasing max.overlaps
Aquí estamos viendo algunos parámetros nuevos:
label me permite agregar las etiquetas de cada
país,
inherit.aes evita que mi texto adopte los colores y
formas de la primera línea.
En el contexto de la exploración de los datos, lo importante es trabajar en forma rápida. Probamos una u otra técnica de visualización y refinamos nuestros resultados hasta hallar patrones interesantes o sacarnos dudas acerca del contenido. No necesitamos ponerle título a las visualizaciones porque ya sabemos de que tratan (¡acabamos de escribirlas!). No nos preocupa que los nombres de los ejes indiquen en forma clara la variable representan porque ya lo sabemos de antemano.
Sin embargo, cuando queremos guardar un gráfico para compartir con otras personas, sea publicándolo en un paper o enviándolo por email a colegas, necesitamos tener más cuidado. Hemos pasado del ámbito de la exploración al de la comunicación. Ahora si nos importa la claridad porque no sabemos de antemano cuánta familiaridad tiene con los datos la eventual audiencia.
Si bien la comunicación clara es un arte cuyas reglas dependen del contexto y además cada quien tiene su estilo, podemos mencionar al menos tres elementos que no deberían faltar en un gráfico pensado para compartir:
y ya que estamos, dos opcionales:
Todo ello puede resolverse con la misma función: labs().
Esta se encarga de definir título y subtítulo, nombres de ejes y de
leyendas y nota al pie si hiciera falta.
Empecemos por título y subtítulo, que se asignan con los parámetros title y subtitle. Pidamos que el título sea “Riqueza vs. salud en los países del mundo”, y el subtítulo, “según datos 2007”:
ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida, size = pobl/1000000, color = continente)) +
geom_point() +
scale_x_log10() +
scale_colour_manual(values = color_continentes) +
theme_minimal() +
labs(title = "Riqueza vs. salud en los países del mundo", subtitle = "según datos 2007")
Ahora mejoremos las etiquetas de los ejes y el tamañno de la leyenda.
La leyenda dec color está bien así, la vamos a dejar en paz. El
parámetro de labs() para definir el nombre que llevaran las
\(x\) es x (como
labs(x = "nombre para el eje de las x")). El parámetro
para las \(y\) es y. El que
fija el título de la leyenda de tamaño es size, y así con el
resto de las leyendas: color, shape, o el atributo que
hayamos definido dentro de aes() al hacer el gráfico.
Pongamos “población (millones)” como título de la leyenda de tamaño, “PBI per capita (USD)” en las \(x\) y “expectativa de vida en años” en las \(y\):
ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida)) +
geom_point(aes(size = pobl/1000000, color = continente)) +
scale_x_log10() +
scale_colour_manual(values = color_continentes) +
theme_minimal() +
labs(title = "Riqueza vs. salud en los países del mundo",
subtitle = "según datos 2007",
size = "población (millones)",
x = "PBI per capita (USD)",
y = "expectativa de vida en años")
Con el paquete ggtext también puedo formatear con Markdown el texto que aparece:
library(ggtext)
ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida)) +
geom_point(aes(size = pobl/1000000, color = continente)) +
scale_x_log10() +
scale_colour_manual(values = color_continentes) +
theme_minimal() +
labs(title = "Riqueza vs. salud en los países del mundo",
subtitle = "según datos 2007",
size = "población (millones)",
x = "**PBI per capita** (USD)",
y = "**expectativa de vida** en años")+
theme(plot.title = element_markdown(face = 'bold'),
axis.title.x = element_markdown(),
axis.title.y = element_markdown())
Y por último, una nota al pie con la fuente de los datos, vía caption :
ggplot(filter(gapminder, año == 2007),
aes(x = PBIpc, y = expVida, size = pobl/1000000, color = continente)) +
geom_point() +
scale_x_log10() +
scale_colour_manual(values = color_continentes) +
theme_minimal() +
labs(title = "Riqueza vs. salud en los países del mundo", subtitle = "según datos 2007",
size = "población (millones)",
x = "**PBI per capita** (USD)",
y = "**expectativa de vida** en años",
caption = "*fuente: Gapminder*") +
theme(plot.title = element_markdown(face = 'bold'),
plot.caption = element_markdown(lineheight = 1.2),
axis.title.x = element_markdown(),
axis.title.y = element_markdown())
ggsave('grafico_gapminder.png', width = 8, height = 5)
Y con eso, terminamos por ahora. Para seguir practicando, tenemos en Data Visualization - A practical introduction de Kieran Healy el capítulo: “Refine your plots”: en el que está basado esta clase.